﻿# define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//1. 操作符的分类
//• 算术操作符： + 、 - 、 * 、 / 、 %
//• 移位操作符 : <<  >>
//• 位操作符 : &, ! ,^
//• 赋值操作符 :=.++,-=,/=,<<=,>>=.&=,|=,^=
//• 单⽬操作符： 1,++,--,&,*,+,-,~,sizeof(类型）
//• 关系操作符 : .,>=,<,<=,==,!=
//• 逻辑操作符： &&,||
//• 条件操作符： exp1 ? exp2 : exp3    b = (a >5 ? 3 : -3) 
//int main()
//{
//	int a = 0;
//	scanf("%d", &a);
//	int b = (a > 5 ? 3 : -3);
//	printf("%d", b);
//	return 0;
// }
//
//• 逗号表达式：,
//• 下标引⽤：[]
//• 函数调⽤：()
//结构成员访问：.、->
//2. ⼆进制和进制转换
//15的2进制：1111
//15的8进制：17
//15的10进制：15
//15的16进制：F
//16进制的数值之前写：0x 
//8进制的数值之前写:0 
//⾸先我们还是得从10进制讲起，其实10进制是我们⽣活中经常使⽤的，我们已经形成了很多常识：
//• 10进制中满10进1
//• 10进制的数字每⼀位都是0~9的数字组成
//其实⼆进制也是⼀样的
//• 2进制中满2进1
//• 2进制的数字每⼀位都是0~1的数字组成
//那么 1101 就是⼆进制的数字了。
//10进制转2进制数字:除2取余法
//例如；10进制都125转化为2进制
//125 / 2 = 62 余1
//62 / 2 = 31  余0
//31 / 2 = 15  余1
//15 / 2 = 7   余1
//7 / 2  =  3  余1
//3 / 2  =  1  余1
//10进制都125转化为2进制的结果为从下往上数   结果为；111101
//2.2 2进制转8进制和16进制
//2.2.1 2进制转8进制：所以在2进制转8进制数的时候，从2进制序列中右边低位开始向左每3个2进制位会换算⼀
//个8进制位，剩余不够3个2进制位的直接换算。
//如：2进制的01101011，换成8进制：0153，0开头的数字，会被当做8进制。
//即 01  101  011
//1    5     3
//2.2.2 2进制转16进制
//16进制的数字每⼀位是0~9, a~f（（a:10，b : 11，c : 12，d : 13，e ：14，f ：15）大写的ABCDEF也可以）的，0~9, a~f的数字，各⾃写成2进制，最多有4个2进制位就⾜够了，
//⽐如f的⼆进制是1111，所以在2进制转16进制数的时候，从2进制序列中右边低位开始向左每4个2进
//制位会换算⼀个16进制位，剩余不够4个⼆进制位的直接换算。
//如：2进制的01101011，换成16进制：0x6b，16进制表⽰的时候前⾯加0x
//0110  1011
//  6      b    16进制；前面加上 0x6b
//3. 原码、反码、补码
//整数的2进制表⽰⽅法有三种，即原码、反码和补码
//有符号整数的三种表⽰⽅法均有符号位和数值位两部分，2进制序列中，最⾼位的1位是被当做符号
//位，剩余的都是数值位。符号位都是⽤0表⽰“正”，⽤1表⽰“负”。
/*1(符号位，1表示负数)     1111111111111111111111111111111{后面31位数值位}
0(符号位，0表示正数)       1111111111111111111111111111111{后面31位数值位}（一共32位）
正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同。
原码：直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码：将原码的符号位不变，其他位依次按位取反就可以得到反码。
补码：反码 + 1就得到补码。
补码得到原码也是可以使⽤：取反 + 1的操作。
例如：
11111111111111111111111111111111 原码
10000000000000000000000000000000 反码（符号位不变，其余位取反）
10000000000000000000000000000001 补码 （反码加1，就是补码）
已知补码求原码
10000000000000000000000000000001 补码
11111111111111111111111111111111 原码（补码取反加1得到）
⽆符号整数的三种 2 进制表⽰相同，没有符号位，每⼀位都是数值位*/
//11111111111111111111111111111111（全是数值位，没有符号位）
//对于整形来说：数据存放内存中其实存放的是补码。
//4. 移位操作符
//<< 左移操作符
//>> 右移操作符
//注：移位操作符的操作数只能是整数。
//.1 左移操作符
//移位规则：左边抛弃、右边补0
//int main()
//{
//	int n = 10;
//	int m = n << 1;
//	/*n:  00000000000000000000000000001010   (10)*/
//	/*m:  0 00000000000000000000000000010100   ()左边抛弃、右边补0*/
//	/*m:  00000000000000000000000000010100     ()二进制表示*/
//	printf("n = %d\n", n);  /*10*/
//	printf("m = %d\n", m);   /*20*/
//	return 0;
//}
//4.2 右移操作符
//移位规则：⾸先右移运算分两种：
//1.逻辑右移：左边⽤0填充，右边丢弃
//2.算术右移：左边⽤原该值的符号位填充，右边丢弃
// 到底是进行算术右移，还是逻辑右移动，主要是看编译器，大多是算术右移
//int main()
//{
//	int n = 10;
//	int m = n >> 1;
//	/*n:  00000000000000000000000000001010   (10)*/     补码表示，，正数的原码，补码，反码相同
//	/*m:  0 0000000000000000000000000000101   ()左边⽤原该值的符号位填充，右边丢弃*/
//	/*m:  00000000000000000000000000000101  (5)这个是算术右移*/  
//
//	/*n:  00000000000000000000000000001010   (10)*/
//	/*m:  0 0000000000000000000000000000101   ()左边⽤0填充，右边丢弃*/
//	/*m:  00000000000000000000000000000101  (-5)这个是逻辑右移*/
//	printf("n = %d\n", n);  /*10*/
//	printf("m = %d\n", m);   /*5*/
//	return 0;
//}
//下面用负数举例
//int main()
//{
//	int n = -1;
//	int m = n >> 1;
//	//	n:        10000000000000000000000000000001  -1的原码
//	//    n :       11111111111111111111111111111110 - 1的反码（符号位不变，其他为按位取反）
//	//	n :       11111111111111111111111111111111 - 1的补码（反码加1）
//	//逻辑右移：
//	//m = n >> 1：  01111111111111111111111111111111 1  逻辑右移：左边⽤0填充，右边丢弃 
//	//              01111111111111111111111111111111    m的补码
//	//              00000000000000000000000000000001    m的原码结果是1
//	//算术右移：
//	//              11111111111111111111111111111111 1   算术右移：左边⽤原该值的符号位填充，右边丢弃
//	//		      11111111111111111111111111111111     m的补码
//	//		      10000000000000000000000000000001     m的原码结果是-1
//	printf("n = %d\n", n);  /*（1）*/
//	printf("m = %d\n", m);    /*(-1)*/
//	return 0;
//}
//警告⚠⚠：对于移位运算符，不要移动负数位，这个是标准未定义的。
//int main()
//{
//	int a = 10;
//     a >> -1;
//	printf("%d", a);
//	return 0;
//}
//5. 位操作符： & 、 | 、 ^ 、~
//位操作符有：（对二进制的补码进行计算）
//&： 按位与 ： 规则：对应的二进制位进行与计算，只要有0则为0，两个1为1.
//|： 按位或 ： 规则：对应的二进制位进行或计算，只要有1则为1，两个0为0.
//^：按位异或 : 规则：对应的二进制位进行比较，相同位0，相异为1., 按位异或支持交换律，a ^ b ^ b = a;任何数字与零异或都是他们本身
//~：按位取反 ：规则：原来是0的变为1，原来是1的变为0
// 注：他们的操作数必须是整数。

//&&：逻辑与，表示并且，要与位操作符做好区分
//||：逻辑或，表示或者
//int main()
//{
//	int a = -7;
//	int b = 8;
//	printf("%d\n", a & b);
//   /* a:    10000000000000000000000000000111     a的原码
//   *        11111111111111111111111111111000     a的反码
//   *        11111111111111111111111111111001     a的补码
//	b:      00000000000000000000000000001000     b的原码，反码相同
//	a & b:  00000000000000000000000000001000*/   /*补码和原码和反码相同,结果为8*/
//	printf("%d\n", a | b);
//	/* a:   10000000000000000000000000000111     a的原码
//   *        11111111111111111111111111111000     a的反码
//   *        11111111111111111111111111111001     a的补码
//	b:      00000000000000000000000000001000     b的原码，反码相同
//	a | b:  11111111111111111111111111111001*/   /* 补码
//	        10000000000000000000000000000111      原码    结果为-7*/
//	printf("%d\n", a ^ b);
//	/* a:   10000000000000000000000000000111     a的原码
//   *        11111111111111111111111111111000     a的反码
//   *        11111111111111111111111111111001     a的补码
//	b:      00000000000000000000000000001000     b的原码，反码相同
//	a ^ b:  11111111111111111111111111110001*/   /* 补码
//			10000000000000000000000000001111      原码    结果为-15*/
//	printf("%d\n", ~a);
//	/* a:   10000000000000000000000000000111     a的原码
//          11111111111111111111111111111000     a的反码
//          11111111111111111111111111111001     a的补码
//     ~a:  00000000000000000000000000000110     原码反码补码相同，结果为6*/
//	printf("%d\n", ~b);
//   /* b:      00000000000000000000000000001000     b的原码，反码相同 
//    ~b:     11111111111111111111111111110111     ~b的补码
//	~b:     10000000000000000000000000001001     ~b的原码，结果为-9*/
//	return 0;
//}
//⼀道变态的⾯试题：
//不能创建临时变量（第三个变量），实现两个整数的交换。
//常规方法设置第三个变量：
//int main()
//{
//	int a = 66;
//	int b = 99;
//	int c = a;
//	a = b;
//	b = c;
//	printf("a = %d\n", a);
//	printf("b = %d\n", b);
//	return 0;
//}
//用按位异或这一位操作符的方式
//int main()
//{
//	int a = 66;
//	int b = 99;
//  a = a ^ b;  
//	b = a ^ b;  /*a^b^b =a*/
//	a = a ^ b;  /*a ^b^b=a^b^a=b*/
//	printf("a = %d\n", a);
//	printf("b = %d\n", b);
//	return 0;
//}
//练习1：编写代码实现：求⼀个整数存储在内存中的⼆进制中1的个数
//int main()
//{
//	int a = 10;
//	int count = 0;
//	while (a)
//	{
//		if (a % 2 == 1)
//			count++;
//		a = a / 2;
//	}
//	printf("二进制中1的个数 = %d\n", count);
//}
//上面代码的局限性：只能求正数，负数无法计算；
//int main()
//{
//	int a = -1;
//	int count = 0;
//	int i = 0;
//	for (i = 0; i < 32; i++)
//	{
//		if (a & (1 << i))
//			count++;
//	}
//	printf("二进制中1的个数 = %d\n", count);
//	return 0;
//}循环次数偏多32次
//int main()
//{
//	int num = 10;
//	int i = 0;
//	int count = 0;//计数 
//	while (num)
//	{
//		count++;
//		num = num & (num - 1);
//	}
//	printf("⼆进制中1的个数 = %d\n", count);
//	return 0;
//}
//练习2：⼆进制位置0或者置1编写代码将13⼆进制序列的第5位修改为1，然后再改回0
//13的二进制序列：        00000000000000000000000000001101
// 1 的二进制序列:        00000000000000000000000000000001
// 1 << 4 的二进制序列:   00000000000000000000000000010000
//~(1 << 4 )的二进制序列: 11111111111111111111111111101111
//将第5位改为1后：        00000000000000000000000000011101   结果为为29
//    再改回0后：         00000000000000000000000000001101
//int main()
//{
//	int a = 13;
//	int b = 1;
//	b = b << 4;
//	printf("b = %d\n", b);
//	a = a | (1 << 4);
//	printf("a = %d\n", a);
//	a = a & ~(1 << 4);
//	printf("a = %d\n", a);
//	return 0;
////}
////6.单⽬操作符单⽬操作符有这些：！、++、--、 & 、 * 、 + 、 - 、~、sizeof、(类型)
//7.逗号表达式   exp1, exp2, exp3, …expN
//逗号表达式，就是⽤逗号隔开的多个表达式。逗号表达式，从左向右依次执⾏。
//整个表达式的结果是最后⼀个表达式的结果
//int main()
//{
//	int a = 1;
//	int b = 2;
//	int c = (a > b, a = b + 10, a, b = a + 1);
//	printf("%d", c);
//	return 0;
//}
//8.下标访问[]、函数调⽤()
//8.1[]下标引⽤操作符
//操作数：⼀个数组名个索引值(下标)
//int arr[10];
//arr[9] = 10;
//[]的两个操作数是9和10；
//8.2函数调⽤操作符:()
//接受⼀个或者多个操作数：第⼀个操作数是函数名，剩余的操作数就是传递给函数的参数。
//9.结构成员访问操作符
//9.1结构体
//C语⾔已经提供了内置类型，如：char、short、int、long、float、double等，
////但是只有这些内置类型还是不够的，假设我想描述学⽣，描述⼀本书，这时单⼀的内置类型是不⾏的。
//9.1.1结构的声明
//struct tag
//{ 
//	   member - list;  
//}variable - list;
//描述⼀个学⽣：
//struct
//{
//	char name[20];
//	int age;
//	char sex[5];//性别
//	char id[20];//学号 
//};
//9.1.2结构体变量的定义和初始化
//struct point
//{
//	int x;
//	int y;
//}p1;//声明类型的同时定义变量p1 
//struct point p2;//定义结构体变量p2 
//struct point p3 = { 66,99 }; /*结构体变量的初始化*/
//struct student
//{
//	char name[20];
//	int age;
//};
//struct student a = { "wangquanfugui",22 };
//struct student b = { .age = 28,.name = "chengpingan" };/*指定顺序初始化*/
//struct node
//{
//	int data;
//	struct point p;
//	struct node* next;
//}n1 = { 10,{4,5},NULL };//结构体嵌套初始化 
//struct node n2 = { 20,{22,33},NULL };//结构体嵌套初始化 
//9.2 结构成员访问操作符
//9.2.1 结构体成员的直接访问
//结构体成员的直接访问是通过点操作符（.）访问的。点操作符接受两个操作数。如下所⽰：
//struct point
//{
//	int x;
//	int y;
//}p = { 1,2 };
//struct point a = { 66, 99 };
//int main()
//{
//	printf("x：%d  y：%d\n", p.x, p.y);
//	printf("x：%d  y：%d\n", a.x, a.y);
//	return 0;
//}
//使⽤⽅式：结构体变量.成员名
//9.2.2 结构体成员的间接访问
//有时候我们得到的不是⼀个结构体变量，⽽是得到了⼀个指向结构体的指针。如下所⽰：
//struct point
//{
//	int x;
//	int y;
//};
//int main()
//{
//	struct point p = { 6, 9 };
//	struct point b = { 3, 4 };
//	struct point* ptr = &p;
//	ptr->x = 10;
//	ptr->y = 20;
//	printf("x = %d y = %d\n", ptr->x, ptr->y);
//	return 0;
//}
//使⽤⽅式：结构体指针->成员名
//综合举例:
//struct student
//{
//	 char name[20];
//	 int age;
//};
//void set_student(struct student* ps);
//void print_student(struct student p)
//{
//	printf("%s %d\n", p.name, p.age);
//}
//void set_student(struct student* ps);
//{
//	strcpy(pa->name, "李四");
//	ps->age = 28;
//}
//int main()
//{
//	struct student p = { "miqieer",28 };
//	print_student(p);
//	set_student(&p);
//	print_student(p);
//	return 0;
//}
//10. 操作符的属性：优先级、结合性
//C语⾔的操作符有2个重要的属性：优先级、结合性，这两个属性决定了表达式求值的计算顺序。
//10.1 优先级
//优先级指的是，如果⼀个表达式包含多个运算符，哪个运算符应该优先执⾏。各种运算符的优先级是
//不⼀样的。
//10.2 结合性
//如果两个运算符优先级相同，优先级没办法确定先计算哪个了，这时候就看结合性了，则根据运算符
//是左结合，还是右结合，决定执⾏顺序。⼤部分运算符是左结合（从左到右执⾏），少数运算符是右
//结合（从右到左执⾏），⽐如赋值运算符（ = ）。
//运算符的优先级顺序很多，下⾯是部分运算符的优先级顺序（按照优先级从⾼到低排列），建议⼤概
//记住这些操作符的优先级就⾏，其他操作符在使⽤的时候查看下⾯表格就可以了。
//• 圆括号（() ）
//• ⾃增运算符（ ++ ），⾃减运算符（ -- ）
//• 单⽬运算符（ + 和 - ）
//• 乘法（ * ），除法（ / ）
//• 加法（ + ），减法（ - ）
//• 关系运算符（ < 、 > 等）
//• 赋值运算符（ = ）
//由于圆括号的优先级最⾼，可以使⽤它改变其他运算符的优先级。
//参考：https ://zh.cppreference.com/w/c/language/operator_precedence
//11. 表达式求值
//11.1 整型提升
//C语⾔中整型算术运算总是⾄少以缺省（默认）整型类型的精度来进⾏的。
//为了获得这个精度，表达式中的字符和短整型操作数在使⽤之前被转换为普通整型，这种转换称为整
//型提升。
//如何进⾏整体提升呢？
//1. 有符号整数提升是按照变量的数据类型的符号位来提升的
//2. ⽆符号整数提升，⾼位补0
////负数的整形提升 
//char c1 = -1;
//变量c1的⼆进制位(补码)中只有8个⽐特位：
//11111111
//因为 char 为有符号的 char
//所以整形提升的时候，⾼位补充符号位，即为1
//提升之后的结果是：
//11111111111111111111111111111111
////正数的整形提升 
//char c2 = 1;
//变量c2的⼆进制位(补码)中只有8个⽐特位：
//00000001
//因为 char 为有符号的 char
//所以整形提升的时候，⾼位补充符号位，即为0
//提升之后的结果是：
//00000000000000000000000000000001
////⽆符号整形提升，⾼位补0 
//11.2 算术转换
//如果某个操作符的各个操作数属于不同的类型，那么除⾮其中⼀个操作数的转换为另⼀个操作数的类
//型，否则操作就⽆法进⾏。下⾯的层次体系称为寻常算术转换。
//long double
//double
//float
//unsigned long int
//long int
//unsigned int
//int
//如果某个操作数的类型在上⾯这个列表中排名靠后，那么⾸先要转换为另外⼀个操作数的类型后执⾏
//运算。